package com.example.log.alarm.repository;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import com.example.log.alarm.advice.logger.LogInfo;
import com.example.log.alarm.advice.exeception.ThrowableInfo;
import com.example.log.alarm.constant.ThrowableConstant;
import com.example.log.alarm.util.WrapperUtil;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @program: spring-agent
 * @description:
 * @author: zt22328 赵儒定
 * @create: 2023-05-05 16:47
 **/
public class MemoryRepository implements Repository {

    private LocalDateTime startTime = LocalDateTime.now();

    /**
     * 异常信息
     */
    private volatile Map<String, ThrowableInfo> throwableInfos = new ConcurrentHashMap<>();

    /**
     * 堆栈信息
     */
    private volatile Map<String, String> stackTraces = new ConcurrentHashMap<>();

    /**
     * 日志
     */
    private volatile Map<String, LogInfo> logInfos = new ConcurrentHashMap<>();

    private List<String> ignoreClasses = Arrays.asList(ClassNotFoundException.class.getName());

    private List<String> ignoreMessages = Arrays.asList("Stack trace", "favicon.ico] cannot be resolved to URL because it does not exist");

    /**
     *
     * @param map
     */
    @Override
    public void putThrowable(Map<String, Object> map) {
        //java.lang.ClassNotFoundException
        //favicon.ico] cannot be resolved to URL because it does not exist
        Throwable throwable = (Throwable) map.get(ThrowableConstant.THROWABLE);
        String clazzName = throwable.getClass().getName();
        String message = throwable.getMessage();
        if (ignoreClasses.contains(clazzName)) {// || containsMessages(message)) {
            return;
        }

        String stackTraceId = genericStackTraceId(throwable);
        String throwableId = WrapperUtil.md5Key(clazzName + ThrowableConstant.SEPARATOR +
                message + ThrowableConstant.SEPARATOR + stackTraceId);
        String threadName = (String) map.get(ThrowableConstant.THREAD_NAME);
        LocalDateTime triggerTime = (LocalDateTime) map.get(ThrowableConstant.TRIGGER_TIME);
        ThrowableInfo throwableInfo = throwableInfos.get(throwableId);
        if (throwableInfo == null) {
            synchronized (throwableId.intern()) {
                throwableInfo = throwableInfos.get(throwableId);
                if (throwableInfo == null) {
                    throwableInfo = new ThrowableInfo();
                    throwableInfo.setThrowableId(throwableId);
                    throwableInfo.setClazz(clazzName);
                    throwableInfo.setMessage(message);
                    throwableInfo.setStackTraceId(stackTraceId);
                    throwableInfo.addThrowableDesc(threadName, triggerTime);
                    throwableInfos.put(throwableId, throwableInfo);
                    return;
                }
            }
        }
        throwableInfo.addThrowableDesc(threadName, triggerTime);
    }

    private boolean containsMessages(String message) {
        for (String ignoreMessage : ignoreMessages) {
            if (message.contains(ignoreMessage)) {
                return true;
            }
        }
        return false;
    }

    private String genericStackTraceId(Throwable throwable) {
        String stackTrace = WrapperUtil.getStackTrace(throwable);
        String stackTraceId = WrapperUtil.md5Key(stackTrace);
        if(!stackTraces.containsKey(stackTraceId)){
            stackTraces.put(stackTraceId, stackTrace);
        }
        return stackTraceId;
    }

    @Override
    public Set<String> getThrowableClasses() {
        return throwableInfos.keySet();
    }

    @Override
    public Collection<ThrowableInfo> getThrowableInfoList(String clazzName) {
        if (StringUtils.hasLength(clazzName)) {
            return throwableInfos.values().stream().filter(e->clazzName.equals(e.getClazz())).collect(Collectors.toList());
        }
        return throwableInfos.values();
    }

    @Override
    public Map<String, String> getStackTraces() {
        return stackTraces;
    }

    @Override
    public synchronized void clear() {
        throwableInfos.clear();
        stackTraces.clear();
    }

    @Override
    public void putLogger(ILoggingEvent event) {
        String levelStr = event.getLevel().levelStr;
        String loggerName = event.getLoggerName();
        String message = event.getFormattedMessage();
        String threadName = event.getThreadName();
        String stackTraceId = genericStackTraceId(event.getThrowableProxy());
        LocalDateTime dateTime = WrapperUtil.timeStamp2LocalDateTime(event.getTimeStamp());
        String loggerId = WrapperUtil.md5Key(loggerName + ThrowableConstant.SEPARATOR +
                levelStr + ThrowableConstant.SEPARATOR + message + ThrowableConstant.SEPARATOR + stackTraceId);
        LogInfo logInfo = logInfos.get(loggerId);
        if (logInfo == null) {
            synchronized (loggerId.intern()) {
                logInfo = logInfos.get(loggerId);
                if (logInfo == null) {
                    logInfo = new LogInfo();
                    logInfo.setLoggerId(loggerId);
                    logInfo.setLoggerName(loggerName);
                    logInfo.setLevel(levelStr);
                    logInfo.setMessage(message);
                    logInfo.setStackTraceId(stackTraceId);
                    logInfo.addLogDesc(threadName, dateTime, event.getMDCPropertyMap());
                    logInfos.put(loggerId, logInfo);
                    return;
                }
            }
        }
        logInfo.addLogDesc(threadName, dateTime, event.getMDCPropertyMap());
    }

    private String genericStackTraceId(IThrowableProxy throwableProxy) {
        if (throwableProxy == null) {
            return null;
        }
        String stackTrace = WrapperUtil.getStackTrace(throwableProxy);
        String stackTraceId = WrapperUtil.md5Key(stackTrace);
        if(!stackTraces.containsKey(stackTraceId)){
            stackTraces.put(stackTraceId, stackTrace);
        }
        return stackTraceId;
    }

    @Override
    public Collection<LogInfo> getLogInfos() {
        return this.logInfos.values();
    }


}
